package org.example.gradleauth.service.impl;

import cn.hutool.core.lang.UUID;
import gradle.common.core.constants.SecurityConstant;
import gradle.common.core.exception.InvalidBearerTokenException;
import gradle.common.core.utils.JwtUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.example.gradleauth.model.dto.JwtClaimDto;
import org.example.gradleauth.model.entity.StaffUserDetails;
import org.example.gradleauth.model.factory.AssembleFactory;
import org.example.gradleauth.model.vo.TokenVo;
import org.example.gradleauth.service.TokenService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

/**
 * TokenServiceImpl
 */
@Slf4j
@Service
public class TokenServiceImpl implements TokenService {

    /**
     * accessToken 有效时间 默认 1小时
     */
    @Value("${token.accessTokenValidity:3600}")
//    @Value("${token.accessTokenValidity:60}")
    private long accessTokenValidity;
    /**
     * refreshToken 有效时间 默认 15天
     */
    @Value("${token.refreshTokenValidity:1296000}")
    private long refreshTokenValidity;

    @Resource
    private UserDetailsServiceImpl userDetailsService;
    @Resource
    private JwtDecoder jwtDecoder;
    @Resource
    private JwtEncoder jwtEncoder;

    /**
     * 生成 AccessToken
     *
     * @param jwtClaimDto jwtClaimDto
     * @return TokenVo
     */
    @Override
    public TokenVo generateAccessToken(JwtClaimDto jwtClaimDto) {

        // @formatter:off
        Instant now = Instant.now();
        Instant accessTokenExpiresAt = now.plusSeconds(accessTokenValidity);
        JwtClaimsSet accessTokenClaims = JwtClaimsSet.builder()
                .id(UUID.randomUUID().toString())
                .issuer(SecurityConstant.ISSUER)
                .issuedAt(now)
                .expiresAt(accessTokenExpiresAt)
                .subject(jwtClaimDto.getUsername())
                .claim(SecurityConstant.AUTHORITIES, jwtClaimDto.getAuthorities())
                .claim(SecurityConstant.USER_ID, jwtClaimDto.getUserId())
                .claim(SecurityConstant.USERNAME, jwtClaimDto.getUsername())
                .build();
        String accessToken = jwtEncoder.encode(JwtEncoderParameters.from(accessTokenClaims)).getTokenValue();
        // @formatter:on

        return TokenVo.builder()
                .accessToken(accessToken)
                .accessTokenExpiresAt(LocalDateTime.ofInstant(accessTokenExpiresAt, ZoneId.systemDefault()))
                .build();
    }

    /**
     * 生成 RefreshToken
     *
     * @param jwtClaimDto jwtClaimDto
     * @return TokenVo
     */
    @Override
    public TokenVo generateRefreshToken(JwtClaimDto jwtClaimDto) {

        // @formatter:off
        Instant now = Instant.now();
        Instant refreshTokenExpiresAt = now.plusSeconds(refreshTokenValidity);
        JwtClaimsSet refreshTokenClaims = JwtClaimsSet.builder()
                .id(UUID.randomUUID().toString())
                .issuer(SecurityConstant.ISSUER)
                .issuedAt(now)
                .expiresAt(now.plusSeconds(refreshTokenValidity))
                .subject(jwtClaimDto.getUsername())
                .claim(SecurityConstant.USER_ID, jwtClaimDto.getUserId())
                .claim(SecurityConstant.USERNAME, jwtClaimDto.getUsername())
                .build();
        String refreshToken = jwtEncoder.encode(JwtEncoderParameters.from(refreshTokenClaims)).getTokenValue();
        // @formatter:on

        return TokenVo.builder()
                .refreshToken(refreshToken)
                .refreshTokenExpiresAt(LocalDateTime.ofInstant(refreshTokenExpiresAt, ZoneId.systemDefault()))
                .build();
    }

    /**
     * 根据 RefreshToken
     * 生成 AccessToken
     *
     * @param refreshToken refreshToken
     * @return TokenVo
     */
    @Override
    public TokenVo refreshToken(String refreshToken) {
        //从请求头提取 JWT Token
        refreshToken = JwtUtil.extractToken(refreshToken);

        Jwt decode;
        try {
            decode = jwtDecoder.decode(refreshToken);
        } catch (JwtException e) {
            log.error("refreshToken exception: {}", ExceptionUtils.getStackTrace(e));
            throw new InvalidBearerTokenException();
        }
        //获取用户信息
        String userName = (String) decode.getClaims().get(SecurityConstant.USERNAME);
        StaffUserDetails userDetails = (StaffUserDetails) userDetailsService.loadUserByUsername(userName);
        //generateAccessToken
        return this.generateAccessToken(AssembleFactory.buildJwtClaim(userDetails));
    }

}
